#include "./can_recv.h"
#include "../conn/can_receiver.h"
#include <string.h>
#include "../conn/proto.h"
#include "../driver/can_bus.h"
#include "../driver/cmd_uart.h"
#include "./master_conn.h"
#include "./salve_conn.h"

namespace service {

using namespace conn;
using namespace driver;

static constexpr uint32_t RECV_BUF_SIZE = 512;
static uint8_t recvBuf[RECV_BUF_SIZE];
static uint32_t recvIndex = 0;

struct MyCanReceiver : public CanReceiver<MyCanReceiver> {

    void on_id_invalid() {}
    void on_much_data() {}

    void on_recv_complete(const ConnInfo &conn_info, const uint8_t *data_buf, uint8_t data_len) {
        if ((recvIndex + data_len) > RECV_BUF_SIZE) {
            recvIndex = 0;
        }
        memcpy(recvBuf + recvIndex, data_buf, data_len);
        Message msg = Message::from_can(conn_info, recvBuf + recvIndex, data_len);
        recvIndex += data_len;

        exec(msg);
    }

    void exec(const Message &msg) {
        ConnInfo conn_info = msg.conn_info;
        if (conn_info.req == proto::REQ_ACK) {
            canBus->on_recv_ack();
            return;
        }
        canBus->write_ack(conn_info.src(), conn_info.seq());

        switch (conn_info.type()) {
            case ConnInfo::TYPE_REQUEST:
                salve_dispatch(msg);
            break;

            case ConnInfo::TYPE_RESPONSE:
                masterConn->dispatch(msg);
            break;
        }
    }
};

static MyCanReceiver myMasterRecv;
static MyCanReceiver myIceRecv;
static MyCanReceiver myPickRecv;
static MyCanReceiver myHeatRecv;
static MyCanReceiver myArmRecv;
static MyCanReceiver myPowderRecv;
static MyCanReceiver myPearlRecv;
static MyCanReceiver myIpc;

void recv_with_my(const conn::CANFrame &frame) {
    switch (frame.src()) {
        case addr::IPC: myIpc.recv(frame); break; 
        case addr::MASTER: myMasterRecv.recv(frame); break;
        case addr::ICE: myIceRecv.recv(frame); break;
        case addr::PICK: myPickRecv.recv(frame); break;
        case addr::HEAT: myHeatRecv.recv(frame); break;
        case addr::ARM: myArmRecv.recv(frame); break;
        case addr::POWDER: myPowderRecv.recv(frame); break;
        case addr::PEARL: myPearlRecv.recv(frame); break;
    }
}


#if defined (BOARD_PIPE_LINE)

struct IpcCanReceiver : public CanReceiver<IpcCanReceiver> {

    void on_id_invalid() {}
    void on_much_data() {}

    void on_recv_complete(const ConnInfo &conn_info, const uint8_t *data_buf, uint8_t data_len) {
        if ((recvIndex + data_len) > RECV_BUF_SIZE) {
            recvIndex = 0;
        }
        memcpy(recvBuf + recvIndex, data_buf, data_len);
        Message msg = Message::from_can(conn_info, recvBuf + recvIndex, data_len);
        recvIndex += data_len;

        exec(msg);
    }

    void exec(const Message &msg) {
        ConnInfo conn_info = msg.conn_info;
        cmdUart->write_data(conn_info, msg.data_buf, msg.data_len);
    }
};

static IpcCanReceiver ipcMasterRecv;
static IpcCanReceiver ipcIceRecv;
static IpcCanReceiver ipcPickRecv;
static IpcCanReceiver ipcHeatRecv;
static IpcCanReceiver ipcArmRecv;
static IpcCanReceiver ipcPowderRecv;
static IpcCanReceiver ipcPearlRecv;

void recv_with_ipc(const conn::CANFrame &frame) {
    switch (frame.src()) {
        case addr::MASTER: ipcMasterRecv.recv(frame); break;
        case addr::ICE: ipcIceRecv.recv(frame); break;
        case addr::PICK: ipcPickRecv.recv(frame); break;
        case addr::HEAT: ipcHeatRecv.recv(frame); break;
        case addr::ARM: ipcArmRecv.recv(frame); break;
        case addr::POWDER: ipcPowderRecv.recv(frame); break;
        case addr::PEARL: ipcPearlRecv.recv(frame); break;
    }
}

#endif

void can_recv_loop() {

    conn::CANFrame frame;

    for (;;) {
        canBus->poll(frame);
        
        if (!frame.is_ext()) {
            continue;
        }
        
#if defined (BOARD_PIPE_LINE)
        switch (frame.dest()) {
            case addr::IPC: 
                recv_with_ipc(frame);
                break;

            case MY_ADDR: 
                recv_with_my(frame);
                break;
        }
#else
        if (frame.dest() != MY_ADDR) {
            continue;
        }
        recv_with_my(frame);
#endif



    }
}

}






